# coding=utf-8
"""
该文件提供系统相关的加解密方法
"""
import enum
import functools
import hashlib
import json
import re
import time as timepy
from datetime import datetime, timedelta
from decimal import Decimal
from random import sample as randomsample
from xml.etree import ElementTree

from setting import is_debug

PRECISION_MODEL = re.compile('^0\.0+$')
DATE_PATTERN = re.compile(r'\d{4}[-/年]\d{1,2}[-/月]\d{1,2}日?')
INT_PATTERN = re.compile(r'^-?\d{1,32}')
DATETIME_PATTERN = re.compile(r'^\d{4}-\d{1,2}-\d{1,2} \d{2}:\d{2}:\d{2}$')


def __util_try_except(fail_value=None):
    def innertrycatch(function):
        @functools.wraps(function)
        def warp(*args, **kwargs):
            try:
                return function(*args, **kwargs)
            except:
                return fail_value

        return warp

    return innertrycatch


def obj_to_dict(obj):
    """
    将对象的属性转换为字典
    :param obj:
    :type obj:
    :return:
    :rtype:
    """
    if not obj or not isinstance(obj, object):
        return {}
    return obj.__dict__


def dic_to_obj(dic):
    """
    将字典转换为对象
    :param dic:
    :type dic:
    :return:
    :rtype:
    """
    top = type('new', (object,), dic)
    seqs = tuple, list, set, frozenset
    for i, j in dic.items():
        if isinstance(j, dict):
            setattr(top, i, dic_to_obj(j))
        elif isinstance(j, seqs):
            setattr(top, i,
                    type(j)(dic_to_obj(sj) if isinstance(sj, dict) else sj for sj in j))
        else:
            setattr(top, i, j)
    return top


def to_string(obj: object, failure_replace_value: object = None, strip_all_blank: object = False) -> object:
    '''
    将对象转换为字符串
    :param obj:带转换的类型
    :type obj:
    :param failure_replace_value:转换失败时代替的类型
    :type failure_replace_value: str
    :param strip_all_blank:是否移除所有空格 ,默认不移除
    :type strip_all_blank: bool
    :return:返回值
    :rtype: str
    '''
    try:
        if obj is None:
            return failure_replace_value

        if isinstance(obj, str):
            new_str = obj
        elif isinstance(obj, bytes):
            new_str = obj.decode(encoding='utf-8')
        elif isinstance(obj, (list, dict)):
            new_str = json.dumps(obj, ensure_ascii=False)
        else:
            new_str = str(obj)

        if strip_all_blank and new_str:
            return new_str.replace(' ', '')

        return new_str
    except:
        return failure_replace_value


def to_byte(str_or_byte):
    '''
    将str转换为bytes
    :param str_or_byte:
    :type str_or_byte:
    :return:
    :rtype:
    '''
    if not str_or_byte:
        return None
    if isinstance(str_or_byte, bytes):
        return str_or_byte
    if isinstance(str_or_byte, str):
        return str_or_byte.encode(encoding='utf-8')
    return None


def to_dict(str_or_byte):
    """
    将字符串或bytes转换为字典
    :param s:
    :type s:
    :return:
    :rtype:
    """
    if isinstance(str_or_byte, dict):
        return str_or_byte
    try:
        return json.loads(to_string(str_or_byte), encoding='utf-8')
    except:
        return {}


def to_int(str_num, failure_result=None):
    '''
    字符串转换为数字
    :param str_num:
    :type str_num:
    :return:
    :rtype:
    '''
    if isinstance(str_num, int):
        return str_num

    if str_num is None:
        return failure_result
    try:
        if isinstance(str_num, enum.Enum):
            return int(str_num.value)

        if isinstance(str_num, (float, Decimal)):
            return int(str_num)

        str_num = to_string(str_num)
        if not str_num:
            return failure_result

        new_str_num = INT_PATTERN.match(str_num)
        if new_str_num:
            return int(new_str_num.group())

        return failure_result
    except Exception as e:
        echo(e, 'to_int')
        return failure_result


def dic_to_list(dic, need_clear=True):
    '''
    字典转数组
    :param dic:
    :type dic:
    :param need_clear:
    :type need_clear:
    :return:
    :rtype:
    '''
    if not dic:
        return []
    res = []
    for v in dic:
        res.append(dic[v])
    if need_clear:
        dic.clear()
    return res


def to_timestamp(str_or_datetime):
    '''
    将时间或者时间字符串转换为时间戳
    :param str_or_datetime:
    :type str_or_datetime:
    :return: int or None
    :rtype: int or None
    '''
    try:
        if isinstance(str_or_datetime, int):
            return str_or_datetime

        if not str_or_datetime:
            return 0

        '''
        datetime->timestamp
        '''
        if isinstance(str_or_datetime, datetime):
            return int(str_or_datetime.timestamp())

        '''
        timeArray->timestamp
        '''
        if isinstance(str_or_datetime, timepy.struct_time):
            return int(timepy.mktime(str_or_datetime))

        '''
        string->timestamp
        '''
        if isinstance(str_or_datetime, str):
            if (get_len(str_or_datetime) > 10):
                timeArray = timepy.strptime(str_or_datetime, "%Y-%m-%d %H:%M:%S")
            else:
                timeArray = timepy.strptime(str_or_datetime, "%Y-%m-%d")
            return int(timepy.mktime(timeArray))

        timeArray = timepy.strptime(str(str_or_datetime), "%Y-%m-%d")
        return int(timepy.mktime(timeArray))
    except:
        return 0


def to_list(obj):
    """
    转换为list
    :param s:
    :type s:
    :return:
    :rtype:
    """
    if isinstance(obj, list):
        return obj
    if isinstance(obj, dict):
        return dic_to_list(obj)
    if isinstance(obj, set):
        return list(obj)
    try:
        return json.loads(to_string(obj), encoding='utf-8')
    except:
        return []


def to_decimal(obj, precision=None, failure_num=0):
    '''
    转换为 Decimal类型
    :param obj:待转数字
    :type obj:object
    :param precision:精度(^0\.0+$) 例如:精度为两位 在传入参数为 0.00
    :type precision: str
    :param failure_num: 是否时候需要返回的值
    :type failure_num: Decimal
    :return: 转换好的数字
    :rtype: Decimal
    '''
    if obj is None:
        return 0

    try:
        if isinstance(obj, Decimal):
            decimal_num = obj

        elif isinstance(obj, int):
            decimal_num = Decimal(obj)

        # elif isinstance(obj, float):
        #     decimal_num = Decimal.from_float(obj)

        elif isinstance(obj, str):
            if r'%' in obj:
                decimal_num = Decimal(obj.replace('%', '')) / 100
            else:
                decimal_num = Decimal(obj)

        else:
            decimal_num = Decimal(to_string(obj, '0'))

        if not precision:
            return decimal_num

        m = PRECISION_MODEL.match(precision)
        if not m:
            return decimal_num

        return decimal_num.quantize(Decimal(m.group()))
    except:
        return failure_num


# --------------------枚举类型相关方法--------------------------------------
def get_enum_value(enum_class, attr_name):
    '''
    根据名字获取枚举值
    :rtype:
    '''
    if not enum_class or not isinstance(enum_class, enum.EnumMeta) or not attr_name or not isinstance(attr_name, str):
        return None
    try:
        return getattr(enum_class, attr_name).value
    except:
        return None


@__util_try_except()
def get_ext(file_name):
    if not file_name:
        return None
    arr = file_name.split('.')
    if not arr or get_len(arr) < 2:
        return None
    arr.reverse()
    for v in arr:
        if v:
            return v
    return None


def to_datetime(time_string, add_day=0):
    '''
    将字符串转换为时间
    :param time_string: 时间字符串
    :param add_day: >0表示向后几天,小于零表示提前几天.
    :return: datetime
    '''

    def convert(time_string):
        if not time_string:
            return None

        if isinstance(time_string, datetime):
            return time_string

        try:
            if isinstance(time_string, str):
                if time_string.find(r'/') > -1:
                    time_string = time_string.replace(r'/', '-')

                if (get_len(time_string) > 10):
                    timeArray = datetime.strptime(time_string, "%Y-%m-%d %H:%M:%S")
                else:
                    timeArray = datetime.strptime(time_string, "%Y-%m-%d")
                return timeArray

            # 时间戳转为时间
            if isinstance(time_string, int):
                date_array = datetime.fromtimestamp(time_string)
                return date_array.strftime("%Y-%m-%d %H:%M:%S")

            return datetime.now()
        except:
            return None

    dtm = convert(time_string)
    if not dtm or not add_day:
        return dtm
    return dtm + timedelta(days=add_day)


def weekends_between(start_date, end_date):
    '''
    计算指定开始到结束时间段内有多少个非工作日
    :param d1:
    :type d1:
    :param d2:
    :type d2:
    :return:
    :rtype:
    '''
    days_between = (end_date - start_date).days
    weekends, leftover = divmod(days_between, 7)
    if leftover:
        start_day = (end_date - timedelta(leftover)).isoweekday()
        end_day = start_day + leftover
        if start_day <= 6 and end_day > 6:
            weekends += 1
        if start_day <= 7 and end_day > 7:
            weekends += 1

    return weekends


def strip_zero(num, failure_num='0'):
    '''
    除去小数点后面多余的零
    '''
    try:
        if num is None:
            return num

        s = str(num)
        if s == '0':
            return '0'

        if '.' not in s:
            return s

        return s.rstrip('0').rstrip('.')
    except:
        return failure_num


def show_date_format(date_obj):
    '''
    本年的（月+日）；跨年的（年+月+日）
    2016-10-13
    '''
    if not date_obj:
        return None
    try:
        this_year = datetime.now().year
        if isinstance(date_obj, str):
            if to_int(date_obj[0:4]) == this_year:
                return to_datetime(date_obj).strftime('%m-%d')
            return to_datetime(date_obj).strftime('%Y-%m-%d')
        if date_obj.year == this_year:
            return date_obj.strftime('%m-%d')
        return date_obj.strftime('%Y-%m-%d')
    except:
        return str(date_obj)


def int_date_now():
    dt = datetime.now()
    month = str(dt.month) if dt.month > 9 else '0' + str(dt.month)
    day = str(dt.day) if dt.day > 9 else '0' + str(dt.day)
    hour = str(dt.hour) if dt.hour > 9 else '0' + str(dt.hour)
    minute = str(dt.minute) if dt.minute > 9 else '0' + str(dt.minute)
    second = str(dt.second) if dt.second > 9 else '0' + str(dt.second)
    return '{0}{1}{2}{3}{4}{5}'.format(dt.year, month, day, hour, minute, second)


def to_int_date(time_string, convert_to_time=False):
    '''
    将时间转换为 20161012 的形式
    :param date_obj:
    :type date_obj:
    :return:
    :rtype: int
    '''
    if isinstance(time_string, int):
        return time_string

    if not time_string:
        return 0

    try:
        if isinstance(time_string, str):
            if time_string.find(r'/') > -1:
                time_string = time_string.replace(r'/', '-')

            # ----------------------------------------
            pattern = r'\d{4}-\d{1,2}-\d{1,2}'
            n = re.match(pattern, time_string)
            if not n:
                return 0

            dt = datetime.strptime(n.group(), "%Y-%m-%d")
        else:
            dt = time_string

        month = str(dt.month) if dt.month > 9 else '0' + str(dt.month)
        day = str(dt.day) if dt.day > 9 else '0' + str(dt.day)

        if not convert_to_time:
            return to_int(str(dt.year) + month + day)

        hour = str(dt.hour) if dt.hour > 9 else '0' + str(dt.hour)
        minute = str(dt.minute) if dt.minute > 9 else '0' + str(dt.minute)
        second = str(dt.second) if dt.second > 9 else '0' + str(dt.second)
        return to_int(str(dt.year) + month + day + hour + minute + second)

    except:
        return 0


def to_float(obj, failure_replace_value=None):
    '''
    转换为浮点数类型
    '''
    try:
        if isinstance(obj, float):
            return obj
        if isinstance(obj, int):
            return obj

        return float(to_string(obj, '0'))
    except:
        return failure_replace_value


@__util_try_except((False, '密码校验失败!'))
def check_pwd_format(pwd):
    failure_result = (False, '密码长度至少为6位,且必须为字母和数字的组合!')
    if not pwd or get_len(pwd) < 6:
        return failure_result

    # ------必须有数字--------------------
    if not re.match(r'[A-Za-z0-9]{6,20}', pwd) or not re.search(r'\d+', pwd) or not re.search(r'[A-Za-z]+', pwd):
        return failure_result

    return (True, '')


@__util_try_except((0, 0, False))
def convert_phone_to_range(phone):
    '''
    将电话号码转换为范围 (要求号码至少3位)
    '''
    if not phone:
        return (0, 0, False)

    n = re.match(r'\d{3,11}', str(phone).replace(' ', ''))
    if not n:
        return (0, 0, False)

    new_phome = n.group()
    l = get_len(new_phome)
    if l == 11:
        return (to_int(new_phome), to_int(new_phome), True)

    min_num = list(new_phome)
    max_num = list(new_phome)
    for v in range(11 - l):
        min_num.append('0')
        max_num.append('9')
    return (to_int(''.join(min_num)) - 1, to_int(''.join(max_num)) + 1, True)


def echo(*args, end='\n'):
    try:
        if not is_debug:
            return
        print(*args, end=end)
    except Exception as e:
        print('echo', e)


def timestamp(to_str=False):
    '''
    获取当前时间戳(精确到秒)
    :return: 时间戳
    :rtype: int
    '''

    if to_str:
        return to_string(int(timepy.time()))
    return int(timepy.time())


def pop_dict(dic, key, replace_value=None):
    '''
    由于从dic中弹出不存在的key会报错,所以添加该方法以简化相关操作
    :param dic:字典
    :type dic: dict
    :param key:key
    :type key:
    :return:
    :rtype:
    '''
    if not dic or not key or not isinstance(dic, dict):
        return None
    if key not in dic:
        return None
    try:
        return dic.pop(key) or replace_value
    except:
        return replace_value


def del_list_ele(ls, ele):
    '''
    删除列表中指定的元素(所有)
    :param ls:列表
    :type ls:list
    :param ele:元素
    :type ele:obj
    :return:
    :rtype:
    '''
    if not ls:
        return True

    assert isinstance(ls, list)
    try:
        while ls and ele in ls:
            ls.remove(ele)
        return True
    except:
        return False


def date_minus(time1, time2):
    '''
    求两时间差(天数)
    '''
    try:
        if not time1 or not time2:
            return 0

        val = time1 - time2
        if not val:
            return 0

        return val.days
    except:
        return 0


def to_date_str(obj, failure_value=''):
    '''
    转换为日期
    :param obj:
    :type obj:
    :param failure_value:
    :type failure_value:
    :return:
    :rtype:
    '''
    try:
        if not obj:
            return ''

        if not isinstance(obj, str):
            obj = to_string(obj)

        gp = re.search(DATE_PATTERN, obj)
        if gp:
            return gp.group()
        return ''
    except:
        return failure_value


def injection_filter(condition):
    '''
    mysql条件检查 (主要针对like)
    :param condition: 查询条件
    :type condition: str
    :return:查询条件
    :rtype:str
    '''
    if not condition:
        return None
    if '=' in condition:
        return None
    if "'" in condition:
        return None
    if ';' in condition:
        return None
    if r'/*' in condition:
        return None
    if r'--' in condition:
        return None
    if r'#' in condition:
        return None
    return condition.strip(' ')


def get_len(obj):
    '''
    获取对象长度
    '''
    if not obj:
        return 0

    try:
        if isinstance(obj, (int, float, Decimal)):
            return len(str(obj).replace('.', '').replace('-', ''))
        return len(obj)
    except:
        return 0


def get_random_chars(length=12, letter_only=False):
    '''
    获取字符串
    :param length:
    :type length:
    :return:
    :rtype:
    '''
    assert isinstance(length, int)
    if length > 60:
        length = 60
    if not letter_only:
        ls = randomsample(r'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0987654321~!@#$%^&*()_+<>?:{},./;\|',
                          length)
    else:
        ls = randomsample(r'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0987654321', length)
    return ''.join(ls)


def get_one_from_dict(dic):
    if not isinstance(dic, dict) or not dic:
        return (None, None)
    for v in dic.items():
        return v


def add_to_dict(dic, key, value):
    '''
    将value添加到字典的key下,可以对应的值是一个字典
    :param dic:字典
    :type dic:dict
    :param key:
    :type key:
    :param value:
    :type value:
    :return:
    :rtype:
    '''
    if dic is None or not key:
        return

    if key in dic:
        dic[key].append(value)
    else:
        dic[key] = [value]


def ravel_list(ls):
    '''
    将多维list展平
    :param ls: list
    :type ls: list
    :return: 展平后的list
    :rtype: list
    '''
    assert ls is None or isinstance(ls, list)

    new_ls = []

    def revl(l):
        for v in l:
            if isinstance(v, list):
                revl(v)
            else:
                new_ls.append(v)

    revl(ls)
    return new_ls


def convert_single_num(num):
    if num == '0':
        return '零'
    if num == '1':
        return '一'
    if num == '2':
        return '二'
    if num == '3':
        return '三'
    if num == '4':
        return '四'
    if num == '5':
        return '五'
    if num == '6':
        return '六'
    if num == '7':
        return '七'
    if num == '8':
        return '八'
    if num == '9':
        return '九'
    return '零'


def get_chs_num(nums):
    if nums is None:
        return '零'
    ls = [convert_single_num(num) for num in str(nums)]
    return ''.join(ls)


def dic_num(dic, field_name):
    if not dic:
        return 0
    return dic.get(field_name) or 0


def format_date_or_decimal(decimal_or_date, convert_to_str=True):
    '''
    将2017年11月20日或Decimal转换为字符串
    注意:当将Decimal转为字符串的时候会保留 2 位小数.
    :param decimal_num:
    :type decimal_num: Decimal
    :param convert_to_str: 是否需要转换为str,乍一看觉得多余,但在好多get_dict中是需要这样操作的.
    :type convert_to_str: bool
    :return:
    :rtype:str
    '''
    if decimal_or_date is None:
        return None

    if not convert_to_str or (convert_to_str and isinstance(decimal_or_date, str)):  # 如果不转为字符串的话就直接返回参数本身.
        return decimal_or_date

    if isinstance(decimal_or_date, datetime):
        return to_string(decimal_or_date)
    if isinstance(decimal_or_date, (float, Decimal)):  # process decimal num
        return to_string(to_decimal(decimal_or_date, '0.00'), '0.00')
    return decimal_or_date  # 只转换decmial和时间,其他的不进行转换.


def convert_intTime_to_str(inttime):
    if not inttime:
        return ''

    if isinstance(inttime, str) and '-' in inttime:
        return inttime

    s = str(inttime)
    if len(s) < 9:
        return '{0}-{1}-{2}'.format(s[:4], s[4:6], s[6:8])
    return '{0}-{1}-{2} {3}:{4}:{5}'.format(s[:4], s[4:6], s[6:8], s[8:10], s[10:12], s[12:14])


def get_int_now():
    dt = datetime.now()
    month = str(dt.month) if dt.month > 9 else '0' + str(dt.month)
    day = str(dt.day) if dt.day > 9 else '0' + str(dt.day)
    hour = str(dt.hour) if dt.hour > 9 else '0' + str(dt.hour)
    minute = str(dt.minute) if dt.minute > 9 else '0' + str(dt.minute)
    second = str(dt.second) if dt.second > 9 else '0' + str(dt.second)
    return to_int(str(dt.year) + month + day + hour + minute + second)


def get_cmd_params(argv_ls):
    dic = {}
    try:
        if not argv_ls:
            return dic

        for v in argv_ls:
            if r'=' not in v:
                continue
            res = v.split('=')
            dic[res[0].strip(' ')] = res[1].strip(' ')
        return dic
    except:
        return dic


def to_sha256(content):
    return hashlib.sha256(to_string(content).encode("utf8")).hexdigest()


def range_validate(mins, maxs, val):
    '''
    取值范围校验
    :param mins: 最小值
    :param maxs: 最大值
    :param val: 实际值
    :return: 最终值
    '''
    assert maxs >= mins
    if val < mins:
        return mins
    if val > maxs:
        return maxs
    return val


def Now():
    return datetime.now()


def to_bool(value):
    """
    转换为 True, False, None. 当值为None或者为考字符串的时候都返回None,其他时候返回bool类型.
    :param value:
    :return:
    """
    if value is None:
        return None

    if isinstance(value, bool):
        return value

    value = str(value).lower().strip()
    if value == '':
        return None

    return value == 'true'


def ls_ele_count(ls):
    """
    返回列表中每个元素出现的次数
    :param ls:
    :return:
    """
    if not ls:
        return {}

    dic = {}
    for v in ls:
        if v in dic:
            dic[v] += 1
        else:
            dic[v] = 1
    return dic


def convert_url(url):
    return url.replace(':', '%3A').replace('/', '%2F')


def xml_to_dict(xml):
    root = ElementTree.fromstring(xml)
    return {child.tag: child.text for child in root}


def calc_avg(n, avg_n, xnp1):
    """
    计算平均值
    :param n:
    :param avg_n: 前n个数的平均值
    :param xnp1: 第n+1个数
    :return: 前n+1个数的平均值
    """
    if n == 0:
        return (1, xnp1)
    return (n + 1, (n * avg_n + xnp1) / (n + 1))


def calc_var(n, avg_n, var_n, avg_np1, xnp1):
    """
    计算方差
    :param n:
    :param avg_n: 前n个数的平均值
    :param var_n:前n个数的方差
    :param avg_np1: 前n+1个数的平均值
    :param xnp1:第n+1个数
    :return:前n+1个数的方差
    """
    if n == 0:
        return 0
    return (n * (avg_np1 ^ 2 - var_n ^ 2) + 2 * (avg_n - avg_np1) * n * avg_n + (xnp1 - avg_np1) ^ 2 + var_n) / (n + 1)


def cut_value(min_limit, max_limit, value):
    return max(min_limit, min(max_limit, value))


def time_str(tm):
    if not tm:
        return ''
    if isinstance(tm, str):
        return tm
    try:
        return tm.strftime("%Y-%m-%d %H:%M:%S")
    except:
        return ''


if __name__ == '__main__':
    print(cut_value(-1, 1, -2))
    print(cut_value(-1, 1, 2))
    print(cut_value(-1, 1, 0))
